<?php
/**
 * Created by PhpStorm.
 * User: lhl
 * Date: 2019/6/4
 * Time: 10:48
 * Features: Excel相关方法封装
 * 依赖库:
 * Excel操作 - composer require phpoffice/phpspreadsheet
 */

namespace app\common\service;

use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
use think\facade\Config;
use think\facade\Request;
use think\facade\Url;
use think\facade\Validate;

class Excel
{
    //临时文件保存路径
    protected static $_savePath = './download/temp/excel';
    //导入允许的文件格式
    protected static $_formatMaps = [
        'xls','xlsx'
    ];


    /**
     * 通用导出excel文件
     * @param array $header 表头 ['字段标题1','字段标题2'...]
     * @param array $data 数据 每一行字段顺序必须与$header保持一致
     * @param string $fileName 保存的文件名称
     * @param string $format 文件格式 xlsx xls csv ods html tcpdf dompdf mpdf
     * @param string $type 处理方式 download : 同步header下载,删除临时文件 url : 返回文件路径,下次请求删除临时目录下所有临时excel文件
     * @param string $sheetName 工作表名
     * @param int $startRow 开始填充表头的起始行数
     * @param string $excelTitle 表头标题(如果有,会加粗显示在第一行)
     * @param string $titleEnd 表头标题合并终止列 如E
     * @return mixed
     */
    public static function export($header,$data,$fileName,$format = 'xlsx',$type = 'download',$sheetName='文件导出',$startRow = 1,$excelTitle = '',$titleEnd = ''){
        set_time_limit(0);
        //文件名称加上unique_id 避免重复
        $fileName = $fileName .'_' .uniqid();
        //创建临时目录
        if (!File::createDir(self::$_savePath)) {
            switch ($type){
                case 'download':
                    exit('导出失败：创建临时目录失败');
                case 'url':
                    return [
                        'code' => 0,
                        'msg' => '导出失败：创建临时目录失败'
                    ];
            }
        }
        //如果是异步返回url的形式,则删除之前的临时目录下的临时文件
        if($type == 'url' && !File::delDir(self::$_savePath)){
            return [
                'code' => 0,
                'msg' => '导出失败：删除临时文件失败'
            ];
        }
        //数据为空
        if(!$data){
            switch ($type){
                case 'download':
                    exit('没有数据');
                case 'url':
                    return [
                        'code' => 0,
                        'msg' => '没有数据'
                    ];
            }
        }

        //生成Excel文件
        $spreadsheet = new Spreadsheet();
        $sheet = $spreadsheet->getActiveSheet(); //当前sheet
        $sheet->setTitle($sheetName); //设置sheet名称
        //设置字体，文字居中
        $spreadsheet->getDefaultStyle()->getFont()->setName('微软雅黑');
        $spreadsheet->getDefaultStyle()->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
        //设置不转换为科学计数法
        $spreadsheet->getDefaultStyle()->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_TEXT);

        //设置Excel大标题并加粗
        if(strlen($excelTitle) && $titleEnd){
            $sheet->setCellValue('A1',$excelTitle);
            //合并单元格,csv格式不支持合并单元格
            $sheet->mergeCells('A1:'.$titleEnd.'1');
            $sheet->getStyle('A1:'.$titleEnd.'1')->getFont()->setBold(true)->setSize(10);
            $sheet->getStyle('A1:'.$titleEnd.'1')->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
        }

        //填充表头 -从$startRow行开始填充
        $header = array_merge($header);
        $count = count($header);
        $offset = 'A';
        for ($i = 0;$i < $count;$i++){
            $sheet->setCellValue($offset.$startRow,$header[$i]);
            $offset++;
        }

        //填充数据 -从$startRow + 1行开始填充
        $rowIndex = $startRow + 1;
        foreach ($data as $key => $value){
            $columIndex = 'A';
            foreach ($value as $k => $v){
                $sheet->setCellValue($columIndex.$rowIndex,$v);
                $columIndex++;
            }
            $rowIndex++;
        }

        //根据格式生成对应Excel文件
        $format = ucfirst($format);
        $writer = IOFactory::createWriter($spreadsheet,$format);
        //下载路径
        $downloadUrl = rtrim(self::$_savePath,'/').'/'.$fileName.'.'.ltrim(lcfirst($format),'.');
        //生成临时文件
        $writer->save($downloadUrl);
        switch ($type){
            //同步下载
            case 'download':
                //下载并删除临时文件
                File::download($downloadUrl);
                exit();
            case 'url':
                //异步返回url
                $downloadUrl = ltrim($downloadUrl,'.');
                return [
                    'code' => 1,
                    'msg' => '导出成功',
                    'path' => $downloadUrl
                ];
        }
    }

    /**
     * 通用excel文件导入,获取文件数据(获取第一个sheet的数据,从第二行开始获取)
     * @param array $header     字段名称,若无则将以A、B.,等作为键名
     * @param string $inputName 传入后端的该文件域变量的键名,默认file
     * @param string $type 'form' : 表单文件域提交  'file' : 文件已存在,直接读取
     * @param string $filePath 文件在服务器本地的路径,若type为'file',则此项必填
     * @return array
     */
    public static function import($header = [],$inputName = 'file',$type = 'form',$filePath = ''){
        set_time_limit(0);
        //创建临时目录
        if (!File::createDir(self::$_savePath)) {
            return [
                'code' => 0,
                'msg' => '导入失败：创建临时目录失败'
            ];
        }
        switch ($type){
            case 'file': //直接读取本地文件
                if(!is_file($filePath)){
                    return [
                        'code' => 0,
                        'msg' => '文件不存在'
                    ];
                }
                //文件后缀
                $suffix = pathinfo($filePath,PATHINFO_EXTENSION);
                break;
            case 'form': //表单上传导入
                $file = isset($_FILES[$inputName]) ? $_FILES[$inputName] : null;
                if(!$file){
                    return [
                        'code' => 0,
                        'msg' => '请上传文件'
                    ];
                }
                if($file['error'] > 0){
                    return [
                        'code' => 0,
                        'msg' => '上传错误:'.$file['error']
                    ];
                }
                $fileName = explode(".", $_FILES[$inputName]['name']);//获取文件后缀
                $_FILES[$inputName]['name'] = date("Ymd-his") .'.'. $fileName['1'];  //修改文件名字为时间戳，解决不能保存中文报错
                $res = move_uploaded_file($_FILES[$inputName]['tmp_name'], rtrim(self::$_savePath,'/').'/' . $_FILES[$inputName]['name']);//移动临时文件到保存路径
                if($res === false){
                    return [
                        'code' => 0,
                        'msg' => '上传失败'
                    ];
                }
                $filePath = rtrim(self::$_savePath,'/') .'/'.$_FILES[$inputName]['name'];//获取保存后的完整文件路径
                $suffix = end($fileName);
                break;
            default:
                return [
                    'code' => 0,
                    'msg' => '未知导入方式'
                ];
        }

        if(!in_array($suffix,self::$_formatMaps)){
            return [
                'code' => 0,
                'msg' => '请上传xls,xlsx格式文件'
            ];
        }
        //开始读取数据
        $reader = IOFactory::createReader(ucfirst($suffix));
        $objExcel = $reader->load($filePath);
        //只读取第一页数据
        $objSheet = $objExcel->getSheet(0);
        //所有行
        $allRow = $objSheet->getHighestRow();
        //所有列
        $allColumn = $objSheet->getHighestColumn();
        // 最大列数+1 实际上是往后错一列
        ++$allColumn;
        $data = [];
        //从第二行开始获取数据
        for ($rowIndex = 2; $rowIndex <= $allRow; $rowIndex++) {
            //每列循环增量
            $headerCol = 0;
            for ($colIndex = 'A'; $colIndex != $allColumn; $colIndex++) {
                $addr = $colIndex . $rowIndex;
                $cell = $objSheet->getCell($addr)->getValue();
                if ($cell instanceof RichText) { //富文本转换字符串
                    $cell = $cell->__toString();
                }
                //判断是否组装头部
                if($header){
                    $data[$rowIndex][$header[$headerCol]] = $cell;
                }else{
                    $data[$rowIndex][$colIndex] = $cell;
                }
                $headerCol++;
            }
        };
        //删除临时文件
        unlink($filePath);
        if(!$data){
            return [
                'code' => 0,
                'msg' => '数据为空'
            ];
        }
        return [
            'code' => 1,
            'msg' => '获取成功',
            'data' => $data
        ];
    }

    /**
     * excel导入数据格式处理
     * 1.整理字段名称
     * 2.表单验证
     * 3.数据内部字段唯一性验证
     * @param array  $header            表头 ['字段中文1' => '字段名1','字段中文2' => '字段名2'...]
     * @param array  $data              数据 每一行字段顺序必须与$header保持一致
     * @param array  $rule              验证器规则
     * @param array  $message           验证器错误提示信息
     * @param array  $uniqueFields      唯一性字段
     *
     * @return
     * [
     *      'success' => 成功数据
     *      'fail' => 失败数据
     * ]
    */
    public static function importDataForamt($header,$data,$rule = [],$message = [],$uniqueFields = []){
        //重新索引
        $headerSet = array_values($header);
        $headerFlip = array_flip($header);
        $data = array_merge($data);
        //整理字段名后的数据
        $dataSet = [];
        foreach ($data as $key => $item){
            $i = 0;
            //整理字段名称
            foreach ($item as $k => $v){
                $dataSet[$key][$headerSet[$i]] = $v;
                $i++;
            }
        }
        unset($data);
        if(!$dataSet){
            return $dataSet;
        }
        //失败数据
        $fail = [];
        //进行表单验证
        if($rule){
            $validate = Validate::make($rule,$message);
        }

        //遍历验证每一行
        foreach ($dataSet as $key => $item){
            //验证表单
            if(isset($validate) && !$validate->check($item)){
                $item['error_msg'] = $validate->getError();
                $fail[] = $item;
                //去掉本行数据
                unset($dataSet[$key]);
                continue;
            }
        }

        //字段重复性验证
        foreach ($uniqueFields as $field){
            //文件内部字段重复性验证
            $repeat = ArrayHelper::arrayUniqueRepeat($dataSet,$field);
            foreach ($repeat as $key => $item){
                if(isset($dataSet[$key])){
                    $fail[] = array_merge($dataSet[$key],[
                        'error_msg' => $headerFlip[$field].'与其他行重复'
                    ]);
                    //去掉错误行
                    unset($dataSet[$key]);
                }
            }
        }

        return [
            'success' => $dataSet,
            'fail' => $fail
        ];
    }

    /**
     * 組裝Excel分页导出的分页条html
     * @param   int     $count   总数据量,自己提前查询好
     * @param   array  $search   搜索参数.作为页码附加参数
     * @return array
     */
    public static function getPageHtml($count,$search = []){
        //分页导出每次最大数据量
        $max = Config::get('admin.export_excel_max_num');
        //判断是否需要分页
        if($count > $max){
            $pageClass = Config::get('paginate.type','think\paginator\driver\Bootstrap');
            //当前页码
            $curPageNum = Request::param(Config::get('paginate.var_page','page'),null);
            $page = new $pageClass([],$max,$curPageNum,$count,false,[
                'path' => Url::build(),
                'query' => $search
            ]);
        }else{
            //不需要分页 则分页条为null
            $page = null;
        }

        return [
            'page' => $page,
            'limit' => $max
        ];
    }

    /**
     * Excel分页: 判断是否为下载文件请求
    */
    public static function isPageDownload(){
        //根据页面来源判断 如果是外部页面请求,则不需要下载excel文件,如果是本页面,则需要下载excel文件（均为异步方式,这里只返回文件路径）
        $referer = Request::header('referer');
        //去除queryString
        $referer = StringHelper::removeQueryStringFromUrl($referer);
        //当前url
        $url = Url::build('','',true,true);
        //url生成最后的方法名会自动转换为小写 将其转换回来
        $action = '/' . Request::action();
        $actionTrue = '/' . Request::action(true);
        $url = str_replace($action,$actionTrue,$url);
        $referer = str_replace($action,$actionTrue,$referer);
        return $referer == $url ? true : false;
    }

}